{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `Nuqleon.Collections.Specialized`\n",
        "\n",
        "Provides specialized collection types."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference the library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 1 - Use a local build\n",
        "\n",
        "If you have built the library locally, run the following cell to load the latest build."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"bin/Debug/net6.0/Nuqleon.Collections.Specialized.dll\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 2 - Use NuGet packages\n",
        "\n",
        "If you want to use the latest published package from NuGet, run the following cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"nuget:Nuqleon.Collections.Specialized,*-*\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## (Optional) Attach a debugger\n",
        "\n",
        "If you'd like to step through the source code of the library while running samples, run the following cell, and follow instructions to start a debugger (e.g. Visual Studio). Navigate to the source code of the library to set breakpoints."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "System.Diagnostics.Debugger.Launch();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Bit arrays\n",
        "\n",
        "Bit arrays provide an efficient storage format for arrays of bits of a specified length. Supported operations are getting and setting bits at the specified index. An example is shown below."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "First, let's create a bit array of the specified size. This will store 24 bits, addressed with indexes 0 through 23."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using System.Collections.Specialized;\n",
        "\n",
        "IBitArray array = BitArrayFactory.Create(size: 24);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To simplify debugging, let's define a helper `Print()` function that simply enumerates over the bit array using the indexer, which returns a `bool` to indicate whether the bit is set or not. When a fresh bit array is allocated, all bits are set to `false`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "000000000000000000000000\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "void Print()\n",
        "{\n",
        "    var sb = new StringBuilder();\n",
        "\n",
        "    for (int i = 0; i < array.Count; i++)\n",
        "    {\n",
        "        sb.Append(array[i] ? '1' : '0');\n",
        "    }\n",
        "\n",
        "    Console.WriteLine(sb.ToString());\n",
        "}\n",
        "\n",
        "Print();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, let's explore the `SetAll` method which provides an easy way to toggle all bits at once."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "111111111111111111111111\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "000000000000000000000000\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "array.SetAll(true);\n",
        "\n",
        "Print();\n",
        "\n",
        "array.SetAll(false);\n",
        "\n",
        "Print();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally, let's have a look at the indexer setter to toggle an individual bit."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "101010101010101010101010\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "for (int i = 0; i < array.Count; i++)\n",
        "{\n",
        "    if (i % 2 == 0)\n",
        "    {\n",
        "        array[i] = true;\n",
        "    }\n",
        "}\n",
        "\n",
        "Print();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**Note:** More bit-wise operations could be added in the future. Because bit arrays are represented using an `IBitArray` interface, the use of operator overloading is problematic. Implementations of additional bit-wise operations can be done efficiently based on the underlying byte arrays that hold the bits."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Enum dictionaries\n",
        "\n",
        "Enum dictionaries provide a memory-efficient storage format for `Dictionary<K, V>` where `K` is an enum type. This can be useful to keep settings where the keys for settings are represented using an enum. Rather than storing the settings using `string`-based names, the use of an enum can be used to optimize the underlying storage format."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We'll first define an enum to be used as the key."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "enum Setting\n",
        "{\n",
        "    Bar,\n",
        "    Foo,\n",
        "    Qux,\n",
        "    Baz,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we'll use the `EnumDictionary.Create<K, V>` method to create an `IDictionary<K, V>` which is optimized for enum keys."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "IDictionary<Setting, string> settings = EnumDictionary.Create<Setting, string>();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The dictionary can be used like any other ordinary dictionary."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Bar = bar\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Foo = foo\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "settings.Add(Setting.Bar, \"bar\");\n",
        "settings[Setting.Foo] = \"foo\";\n",
        "\n",
        "foreach (var (key, value) in settings)\n",
        "{\n",
        "    Console.WriteLine($\"{key} = {value}\");\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To illustrate the potential memory saving, we run a tiny benchmark in the next cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "EnumDictionary - 288 bytes allocated\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "BCL Dictionary - 464 bytes allocated\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "using System.Threading;\n",
        "\n",
        "static void Populate(IDictionary<Setting, string> settings)\n",
        "{\n",
        "    settings.Add(Setting.Bar, \"bar\");\n",
        "    settings.Add(Setting.Foo, \"foo\");\n",
        "    settings.Add(Setting.Qux, \"qux\");\n",
        "    settings.Add(Setting.Baz, \"baz\");\n",
        "}\n",
        "\n",
        "static void Measure(string title, Func<IDictionary<Setting, string>> createDictionary, bool print)\n",
        "{\n",
        "    var t = new Thread(() =>\n",
        "    {\n",
        "        long mem = GC.GetAllocatedBytesForCurrentThread();\n",
        "\n",
        "        IDictionary<Setting, string> settings = createDictionary();\n",
        "\n",
        "        Populate(settings);\n",
        "\n",
        "        if (print)\n",
        "        {\n",
        "            Console.WriteLine($\"{title} - {GC.GetAllocatedBytesForCurrentThread() - mem} bytes allocated\");\n",
        "        }\n",
        "    });\n",
        "    t.Start();\n",
        "    t.Join();\n",
        "}\n",
        "\n",
        "// NB: We run a warm-up iteration first that doesn't print to exclude allocation side-effects from e.g. running static initializers.\n",
        "\n",
        "foreach (var print in new[] { false, true })\n",
        "{\n",
        "    Measure(\"EnumDictionary\", () => EnumDictionary.Create<Setting, string>(), print);\n",
        "    Measure(\"BCL Dictionary\", () => new Dictionary<Setting, string>(), print);\n",
        "}"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".NET (C#)",
      "language": "C#",
      "name": ".net-csharp"
    },
    "language_info": {
      "file_extension": ".cs",
      "mimetype": "text/x-csharp",
      "name": "C#",
      "pygments_lexer": "csharp",
      "version": "9.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}